home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-01
/
ohlutil.zip
/
RM.C
< prev
next >
Wrap
C/C++ Source or Header
|
1990-06-22
|
14KB
|
567 lines
/* `rm' file deletion utility for GNU.
Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* Written by Paul Rubin, David MacKenzie, and Richard Stallman. */
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include "system.h"
#include "getopt.h"
#ifdef STDC_HEADERS
#include <stdlib.h>
#include <errno.h>
#else
char *malloc ();
char *realloc ();
extern int errno;
#endif
char *basename ();
char *stpcpy ();
char *xmalloc ();
char *xrealloc ();
int check_stack ();
int clear_directory ();
int remove_dir ();
int remove_file ();
int rm ();
int yesno ();
void error ();
void strip_trailing_slashes ();
void usage ();
/* Path of file now being processed; extended as necessary. */
char *pathname;
/* Amount of space currently allocated in `pathname';
made larger when necessary, but never smaller. */
int pnsize;
/* Name under which this program was run. */
char *program_name;
/* If nonzero, display the name of each file removed. */
int verbose;
/* If nonzero, ignore unremovable files (print no error messages and
always exit with a status of zero). */
int ignore_errors;
/* If nonzero, recursively remove directories. */
int recursive;
/* If nonzero, ask no questions. */
int override_mode;
/* If nonzero, query the user about whether to remove each file. */
int interactive;
/* If nonzero, stdin is not connected to a tty. */
int stdin_not_tty;
struct option long_opts[] =
{
{"force", 0, NULL, 'f'},
{"interactive", 0, &interactive, 1},
{"override", 0, &override_mode, 1},
{"recursive", 0, &recursive, 1},
{"verbose", 0, &verbose, 1},
{NULL, 0, NULL, 0}
};
void
main (argc, argv)
int argc;
char **argv;
{
int err = 0;
int c;
int ind;
verbose = ignore_errors = recursive = interactive = override_mode = 0;
pnsize = 256;
pathname = xmalloc (pnsize);
program_name = argv[0];
while ((c = getopt_long (argc, argv, "fiorvR", long_opts, &ind)) != EOF)
{
if (c == 0 && long_opts[ind].flag == 0)
c = long_opts[ind].val;
switch (c)
{
case 0: /* Long option. */
break;
case 'f':
ignore_errors = 1;
override_mode = 1;
break;
case 'i':
interactive = 1;;
break;
case 'o':
override_mode = 1;
break;
case 'r':
case 'R':
recursive = 1;
break;
case 'v':
verbose = 1;
break;
default:
usage ();
}
}
if (optind == argc)
usage ();
if (interactive)
{
override_mode = 0;
ignore_errors = 0;
}
stdin_not_tty = !isatty (0);
for (; optind < argc; optind++)
{
int len;
strip_trailing_slashes (argv[optind]);
if (!strcmp (basename (argv[optind]), ".."))
{
if (!ignore_errors)
error (0, 0, "removal of `%s' is not allowed", argv[optind]);
err++;
continue;
}
len = strlen (argv[optind]);
if (len + 1 > pnsize)
{
free (pathname);
pnsize = 2 * (len + 1);
pathname = xmalloc (pnsize);
}
strcpy (pathname, argv[optind]);
err += rm ();
}
exit (err > 0 && !ignore_errors);
}
/* Remove file or directory `pathname' after checking appropriate things.
Return 0 if `pathname' is removed, 1 if not. */
int
rm ()
{
struct stat sbuf;
if (lstat (pathname, &sbuf) < 0)
{
if (!ignore_errors)
error (0, errno, "%s", pathname);
return 1;
}
if (verbose)
printf (" %s\n", pathname);
if ((sbuf.st_mode & S_IFMT) == S_IFDIR)
return remove_dir (&sbuf);
else
return remove_file (&sbuf);
}
/* Query the user if appropriate, and if ok try to remove the
non-directory `pathname', which `statp' contains info about.
Return 0 if `pathname' is removed, 1 if not. */
int
remove_file (statp)
struct stat *statp;
{
if (interactive)
{
fprintf (stderr, "%s: remove `%s'? ", program_name, pathname);
if (!yesno ())
return 1;
}
else if (!override_mode)
{
int may_overwrite;
/* Treat the file as nonwritable if it lacks write permission bits,
even if we are root. */
#ifdef S_IFLNK
if ((statp->st_mode & S_IFMT) == S_IFLNK)
may_overwrite = 1;
else
#endif
may_overwrite = eaccess_stat (statp, W_OK) == 0
&& (statp->st_mode & 0222);
if (!may_overwrite)
{
if (stdin_not_tty)
{
if (!ignore_errors)
error (0, 0, "%s: no write permission", pathname);
return 1;
}
fprintf (stderr, "%s: override mode %04o for `%s'? ",
program_name, statp->st_mode & 0777, pathname);
if (!yesno ())
return 1;
}
}
if (unlink (pathname))
{
if (!ignore_errors)
error (0, errno, "%s", pathname);
return 1;
}
return 0;
}
/* If not in recursive mode, print an error message and return 1.
Otherwise, query the user if appropriate, then try to recursively
remove directory `pathname', which `statp' contains info about.
Return 0 if `pathname' is removed, 1 if not. */
int
remove_dir (statp)
struct stat *statp;
{
int err;
if (!recursive)
{
if (!ignore_errors)
error (0, 0, "`%s' is a directory", pathname);
return 1;
}
if (eaccess_stat (statp, R_OK | X_OK))
{
/* POSIX.2 draft 9 says to only complain here if the dir is
not empty, but I can see no way to find out whether it is
empty without being able to read it. */
if (!ignore_errors)
error (0, errno, "%s", pathname);
return 1;
}
if (interactive)
{
fprintf (stderr, "%s: recursively descend directory `%s'? ",
program_name, pathname);
if (!yesno ())
return 1;
}
err = clear_directory (statp);
if (err == 0)
{
if (interactive)
{
fprintf (stderr, "%s: remove directory `%s'? ",
program_name, pathname);
if (!yesno ())
return 1;
}
err = rmdir (pathname) != 0;
if (err != 0 && !ignore_errors)
error (0, errno, "%s", pathname);
}
return err;
}
/* An element in a stack of pointers into `pathname'.
`pathp' points to where in `pathname' the terminating '\0' goes
for this level's directory name. */
struct pathstack
{
struct pathstack *next;
char *pathp;
ino_t inum;
};
/* Linked list of pathnames of directories in progress in recursive rm.
The entries actually contain pointers into `pathname'.
`pathstack' is the current deepest level. */
static struct pathstack *pathstack = NULL;
/* Read directory `pathname' and remove all of its entries,
avoiding use of chdir.
On entry, `statp' points to the results of stat on `pathname'.
Return 0 for success, error count for failure.
Upon return, `pathname' will have the same contents as before,
but its address might be different; in that case, `pnsize' will
be larger, as well. */
int
clear_directory (statp)
struct stat *statp;
{
DIR *dirp;
struct direct *dp;
char *name_space; /* Copy of directory's filenames. */
char *namep; /* Current entry in `name_space'. */
unsigned name_size; /* Bytes allocated for `name_space'. */
ino_t *inode_space; /* Copy of directory's inodes. */
ino_t *inodep; /* Current entry in `inode_space'. */
unsigned inode_size; /* Bytes allocated for `inode_space'. */
int name_length; /* Length of filename in `namep' plus '\0'. */
int pathname_length; /* Length of `pathname'. */
int err = 0; /* Return status. */
struct pathstack pathframe; /* New top of stack. */
struct pathstack *pp; /* Temporary. */
errno = 0;
dirp = opendir (pathname);
if (dirp == NULL)
{
if (!ignore_errors)
error (0, errno, "%s", pathname);
return 1;
}
name_size = statp->st_size;
name_space = (char *) xmalloc (name_size);
namep = name_space;
inode_size = statp->st_size;
inode_space = (ino_t *) xmalloc (inode_size);
inodep = inode_space;
while ((dp = readdir (dirp)) != NULL)
{
/* Skip "." and ".." (some NFS filesystems' directories lack them). */
if (dp->d_name[0] != '.'
|| (dp->d_name[1] != '\0'
&& (dp->d_name[1] != '.' || dp->d_name[2] != '\0')))
{
unsigned size_needed = (namep - name_space) + NLENGTH (dp) + 2;
if (size_needed > name_size)
{
char *new_name_space;
while (size_needed > name_size)
name_size += 1024;
new_name_space = xrealloc (name_space, name_size);
namep += new_name_space - name_space;
name_space = new_name_space;
}
namep = stpcpy (namep, dp->d_name) + 1;
if (inodep == inode_space + inode_size)
{
ino_t *new_inode_space;
inode_size += 1024;
new_inode_space = (ino_t *) xrealloc (inode_space, inode_size);
inodep += new_inode_space - inode_space;
inode_space = new_inode_space;
}
*inodep++ = dp->d_ino;
}
}
*namep = '\0';
closedir (dirp);
pathname_length = strlen (pathname);
for (namep = name_space, inodep = inode_space; *namep != '\0';
namep += name_length, inodep++)
{
name_length = strlen (namep) + 1;
/* Satisfy GNU requirement that filenames can be arbitrarily long. */
if (pathname_length + 1 + name_length > pnsize)
{
char *new_pathname;
pnsize = (pathname_length + 1 + name_length) * 2;
new_pathname = xrealloc (pathname, pnsize);
/* Update the all the pointers in the stack to use the new area. */
for (pp = pathstack; pp != NULL; pp = pp->next)
pp->pathp += new_pathname - pathname;
pathname = new_pathname;
}
/* Add a new frame to the top of the path stack. */
pathframe.pathp = pathname + pathname_length;
pathframe.inum = *inodep;
pathframe.next = pathstack;
pathstack = &pathframe;
/* Append '/' and the filename to current pathname, take care of the
file (which could result in recursive calls), and take the filename
back off. */
*pathstack->pathp = '/';
strcpy (pathstack->pathp + 1, namep);
/* If the i-number has already appeared, there's an error. */
if (check_stack (pathstack->next, pathstack->inum) || rm ())
err++;
*pathstack->pathp = '\0';
pathstack = pathstack->next; /* Pop the stack. */
}
free (name_space);
free (inode_space);
return err;
}
/* If STACK does not already have an entry with the same i-number as INUM,
return 0. Otherwise, ask the user whether to continue;
if yes, return 1, and if no, exit.
This assumes that no one tries to remove filesystem mount points;
doing so could cause duplication of i-numbers that would not indicate
a corrupted file system. */
int
check_stack (stack, inum)
struct pathstack *stack;
ino_t inum;
{
struct pathstack *p;
for (p = stack; p != NULL; p = p->next)
{
if (p->inum == inum)
{
fprintf (stderr, "\
%s: WARNING: Circular directory structure.\n\
This almost certainly means that you have a corrupted file system.\n\
NOTIFY YOUR SYSTEM MANAGER.\n\
Cycle detected:\n\
%s\n\
is the same file as\n", program_name, pathname);
*p->pathp = '\0'; /* Truncate pathname. */
fprintf (stderr, "%s\n", pathname);
*p->pathp = '/'; /* Put it back. */
if (stdin_not_tty)
exit (1);
fprintf (stderr, "%s: continue? ", program_name);
if (!yesno ())
exit (1);
return 1;
}
}
return 0;
}
/* Query the user for a line from the keyboard;
return 1 if yes, 0 otherwise. */
int
yesno ()
{
int c, c2;
fflush (stderr);
c = getchar ();
if (c == '\n')
return 0;
while ((c2 = getchar ()) != '\n' && c2 != EOF)
;
return c == 'y' || c == 'Y';
}
/* Remove trailing slashes from PATH; they cause some system calls to fail. */
void
strip_trailing_slashes (path)
char *path;
{
int last;
last = strlen (path) - 1;
while (last > 0 && path[last] == '/')
path[last--] = '\0';
}
char *
xmalloc (n)
unsigned n;
{
char *p;
p = malloc (n);
if (p == 0)
error (2, 0, "virtual memory exhausted");
return p;
}
char *
xrealloc (p, n)
char *p;
unsigned n;
{
p = realloc (p, n);
if (p == 0)
error (2, 0, "virtual memory exhausted");
return p;
}
/* Return `name' with any leading path stripped off. */
char *
basename (name)
char *name;
{
char *base;
base = rindex (name, '/');
return base ? base + 1 : name;
}
/* Copy SOURCE into DEST, stopping after copying the first '\0', and
return a pointer to the '\0' at the end of DEST;
in other words, return DEST + strlen (SOURCE). */
char *
stpcpy (dest, source)
char *dest;
char *source;
{
while ((*dest++ = *source++) != 0)
/* Do nothing. */ ;
return dest - 1;
}
void
usage ()
{
fprintf (stderr, "\
Usage: %s [-fiorvR] [+force] [+interactive] [+override] [+recursive]\n\
[+verbose] path...\n",
program_name);
exit (1);
}